package ldk.util.slipview

import android.content.Context
import android.support.v4.view.MotionEventCompat
import android.support.v4.view.ViewCompat
import android.support.v4.view.ViewPropertyAnimatorListener
import android.support.v4.widget.ViewDragHelper
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup

/**
 * The SlipView can Slip View
 * Created by ldkxingzhe@163.com on 2017/8/23.
 */
open class SlipView(context: Context?, attrs: AttributeSet? = null)
    : ViewGroup(context, attrs) {

    companion object{
        val DEBUG = true
        val TAG = "SlipRootView"

        val STATE_IDLE = ViewDragHelper.STATE_IDLE
        val STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING
        val STATE_SETTING = ViewDragHelper.STATE_SETTLING

        val EDGE_LEFT = ViewDragHelper.EDGE_LEFT
        val EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT
        val EDGE_TOP = ViewDragHelper.EDGE_TOP
        val EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM


        private val TOUCH_SLOP_SENSITIVITY = 1.0f
        private val MIN_FLING_VELOCITY = 400 // dips per second
    }

    private var mDragger: ViewDragHelper
    private var mDragCallback: ViewDragCallback

    private var mDrawerState = STATE_IDLE
    private var mInLayout = false
    private var mFirstLayout = true

    private var mInitialMotionX = -1f
    private var mInitialMotionY = -1f
    private var mDownTime = -1L
    private var mEventTime = -1L

    private var mMinBackPercent = 0.3f
    private var mTouchSlop: Int

    var eatScroll: Boolean
    var edge = EDGE_LEFT
        set(value) {
            mDragger.setEdgeTrackingEnabled(value)
            field = value
        }

    var offsetChangeFunction: ((Float)->Void)? = null

    init {
        val density = resources.displayMetrics.density
        val minVel = MIN_FLING_VELOCITY * density
        mDragCallback = ViewDragCallback()
        mDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mDragCallback)
        mDragger.minVelocity = minVel
        mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)

        // So that we catch the back button
        isFocusableInTouchMode = true
        ViewCompat.setImportantForAccessibility(this,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES)
        mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
        val array = context!!.obtainStyledAttributes(attrs, R.styleable.SlipRootView)
        eatScroll = array.getBoolean(R.styleable.SlipRootView_eatScroll, true)
        array.recycle()
        setWillNotDraw(true)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        mFirstLayout = true
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        mFirstLayout = false
    }

    private inline fun getChildLayoutParams(): LayoutParams{
        val child = getChildAt(0)
        return child.layoutParams as LayoutParams
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val widthModel = MeasureSpec.getMode(widthMeasureSpec)
        val heightModel = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        if (widthModel != MeasureSpec.EXACTLY
                || heightModel != MeasureSpec.EXACTLY){
            throw IllegalStateException("SlipRotView must be measured with MeasureSpec.EXACTLY")
        }

        if (childCount != 1)
            throw IllegalStateException("SlipRootView only support one child")
        setMeasuredDimension(widthSize, heightSize)
        val child = getChildAt(0)
        val lp: LayoutParams = child.layoutParams as LayoutParams
        val contentWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY)
        val contentHeightSpec = MeasureSpec.makeMeasureSpec(heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY)
        child.measure(contentWidthSpec, contentHeightSpec)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        mInLayout = true
        val child = getChildAt(0)

        if (child.visibility != View.GONE){
            val lp = child.layoutParams as LayoutParams
            child.layout(lp.leftMargin, lp.topMargin,
                    lp.leftMargin + child.measuredWidth,
                    lp.topMargin + child.measuredHeight)

        }
    }

    private fun updateState(state: Int){
        mDrawerState = mDragger.viewDragState

        if (state == STATE_IDLE){
            val child = getChildAt(0)
            val lp = child.layoutParams as LayoutParams

            if (DEBUG){
                Log.v(TAG, "updateState, and lp.onScreen is ${lp.onScreen}")
            }

            if (lp.onScreen == 0f){

            }else if (lp.onScreen == 1f){
                onPageFinished()
            }else{
                Log.w(TAG, "lp.onScreen is ${lp.onScreen}, must be 0 or 1");
            }
        }

    }

    open fun finishPageWithAnimation(){
        val animator = ViewCompat.animate(getChildAt(0))
        if (isHorizontal()){
            animator.translationX(width.toFloat())
        }else{
            animator.translationY(height.toFloat())
        }
        animator.duration = 300
        animator.setUpdateListener {
            onXYChanged(it.translationX , it.translationY)
        }
        animator.setListener(object : ViewPropertyAnimatorListener {
            override fun onAnimationEnd(view: View?) {
                if (DEBUG){
                    Log.v(TAG, "onAnimationEnd")
                }
                onXYChanged(width.toFloat(), height.toFloat())
                onPageFinished()
            }

            override fun onAnimationCancel(view: View?) {

            }

            override fun onAnimationStart(view: View?) {
                if (DEBUG){
                    Log.v(TAG, "onAnimationStart")
                }
            }

        })
        animator.start()
    }

    internal  fun calculateHorizontalOffset(translationX: Float): Float{
        val offset = translationX / width
        return if (isRevertOffset()) 1 - offset else offset
    }

    internal  fun calculateVerticalOffset(translationY: Float): Float{
        val offset = translationY / height
        return if (isRevertOffset()) 1 - offset else offset
    }

    internal  fun isHorizontal(): Boolean{
        return (edge == EDGE_LEFT) || (edge == EDGE_RIGHT)
    }

    internal  fun isRevertOffset(): Boolean{
        return (edge == EDGE_BOTTOM) || (edge == EDGE_RIGHT)
    }

    private fun onXYChanged(x: Float, y: Float){
        val offset = if (isHorizontal()) calculateHorizontalOffset(x) else calculateVerticalOffset(y)
        val lp = getChildLayoutParams()
        if (lp.onScreen == offset) return
        lp.onScreen = offset
        background?.alpha = ((1- offset) * 255).toInt()
        onOffsetChanged(offset);
    }

    open internal fun onOffsetChanged(offset: Float){
        offsetChangeFunction?.invoke(offset)
    }

    open internal fun onPageFinished() {

    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        if (ev == null || !isEnabled) return super.onInterceptTouchEvent(ev)

        val action = MotionEventCompat.getActionMasked(ev)
        val interceptForDrag = mDragger.shouldInterceptTouchEvent(ev)
        var interceptForScroll = false

        when(action){
            MotionEvent.ACTION_DOWN ->{
                mInitialMotionX = ev.x
                mInitialMotionY = ev.y
                mDownTime = ev.downTime
                mEventTime = ev.eventTime
            }

            MotionEvent.ACTION_MOVE ->{
                val xDiff = ev.x - mInitialMotionX
                val yDiff = ev.y - mInitialMotionY

                if (eatScroll && (if (isHorizontal()){
                    if (isRevertOffset()){
                        xDiff < mTouchSlop && -xDiff * 0.5f > Math.abs(yDiff)
                    }else{
                        xDiff > mTouchSlop && xDiff * 0.5f > Math.abs(yDiff)
                    }
                }else{
                    if (isRevertOffset()){
                        yDiff < mTouchSlop && -yDiff * 0.5f > Math.abs(xDiff)
                    }else{
                        yDiff > mTouchSlop && yDiff * 0.5f > Math.abs(xDiff)
                    }
                })){
                    interceptForScroll = true
                    try{
                        val fakeEvent = MotionEvent.obtain(mDownTime, mEventTime, MotionEvent.ACTION_DOWN, mInitialMotionX, mInitialMotionY, 0)
                        mDragger.processTouchEvent(fakeEvent)
                    }catch (e: Exception){
                        Log.e(TAG, e.message, e);
                    }
                }
            }
        }

        if (DEBUG){
            Log.v(TAG, "onInterceptTouchEvent, and $interceptForDrag $interceptForScroll")
        }
        return interceptForDrag || interceptForScroll;
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (!isEnabled) return super.onTouchEvent(event)
        if (event == null){
            if (DEBUG){
                throw IllegalStateException("unknown reason , but event is null")
            }
            return false
        }

        mDragger.processTouchEvent(event)
        return true
    }

    override fun computeScroll() {
        if (mDragger.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(this)
        }
    }

    override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {
        return LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
    }

    override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
        return when(p){
            is LayoutParams -> LayoutParams(p)
            is ViewGroup.MarginLayoutParams -> LayoutParams(p)
            else -> LayoutParams(p)
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?): ViewGroup.LayoutParams {
        return LayoutParams(context, attrs)
    }


    inner class LayoutParams : ViewGroup.MarginLayoutParams{

        internal var onScreen = 0f

        constructor(c: Context?, attrs: AttributeSet?) : super(c, attrs)
        constructor(width: Int, height: Int) : super(width, height)
        constructor(source: MarginLayoutParams?) : super(source)
        constructor(source: ViewGroup.LayoutParams?) : super(source)
    }

    inner class ViewDragCallback : ViewDragHelper.Callback(){
        override fun tryCaptureView(child: View?, pointerId: Int): Boolean {
            if (DEBUG){
                Log.v(TAG, "tryCaptureView, and pointerId: $pointerId")
            }
            return true
        }

        override fun onViewDragStateChanged(state: Int) {
            super.onViewDragStateChanged(state)
            if (DEBUG){
                Log.v(TAG, "onViewDragStateChanged $state")
            }
            updateState(state)
        }

        override fun onViewReleased(releasedChild: View?, xvel: Float, yvel: Float) {
            super.onViewReleased(releasedChild, xvel, yvel)
            if (DEBUG){
                Log.v(TAG, "onViewReleased: x=$xvel, y=$yvel")
            }
            val offset = (releasedChild!!.layoutParams as LayoutParams).onScreen

            if (isHorizontal()){
                val left = if (xvel > 0 || xvel.toInt() == 0 && offset > mMinBackPercent) width else 0
                mDragger.settleCapturedViewAt(left, 0)
            }else{
                val top = if (yvel > 0 || yvel.toInt() == 0 && offset > mMinBackPercent) height else 0
                mDragger.settleCapturedViewAt(0, top)
            }
            invalidate()
        }


        override fun onViewPositionChanged(changedView: View?, left: Int, top: Int, dx: Int, dy: Int) {
            if (DEBUG){
                Log.v(TAG, "onViewPositionChanged, $left, $top, $dx, $dy")
            }
            onXYChanged(left.toFloat(), top.toFloat())
        }

        override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
            super.onEdgeDragStarted(edgeFlags, pointerId)
            if (DEBUG){
                Log.v(TAG, "onEdgeDragStarted, and edgeFlags is $edgeFlags")
            }
            mDragger.captureChildView(getChildAt(0), pointerId)
        }

        override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int {
            return if (isHorizontal()) Math.max(Math.min(left, width), 0) else super.clampViewPositionHorizontal(child, left, dx)
        }

        override fun clampViewPositionVertical(child: View?, top: Int, dy: Int): Int {
            return if (isHorizontal()) super.clampViewPositionVertical(child, top, dy) else Math.max(Math.min(top, height), 0)
        }
    }
}